package top.as.sean.common.gson;

import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializer;
import com.google.gson.LongSerializationPolicy;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.internal.bind.ObjectTypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static java.time.format.DateTimeFormatter.ofPattern;

/**
 * gson工具类
 *
 * @author: ArchieSean
 * @date: 2022-09-07 15:12
 */
@Slf4j
@Component
public class GsonUtil {

    private GsonUtil() {
    }

    /**
     * <p>
     * 配置属性注解 @SerializedName(value = "json_KEY")用于指定属性和json-key之间的关系</p>
     * <p>@Expose注解可以设置序列化和反序列化，对应两个属性serialize,deserialize</p>
     */
    private static final Gson GSON;
    private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
    private static final String DATEFORMAT = "yyyy-MM-dd";
    private static final String TIMEFRAME = "HH:mm:ss";
    private static final JsonSerializer<LocalDateTime> DATE_TIME_SERIALIZER =
            (obj, type, ctx) -> new JsonPrimitive(DateUtil.format(obj, FORMAT));
    private static final JsonSerializer<LocalDate> DATE_SERIALIZER =
            (obj, type, ctx) -> new JsonPrimitive(DateUtil.format(
                    LocalDate.parse(obj.toString(), DateTimeFormatter.ofPattern(DATEFORMAT)).atStartOfDay(),
                    DATEFORMAT));
    private static final JsonSerializer<LocalTime> TIME_SERIALIZER =
            (obj, type, ctx) -> new JsonPrimitive(DateUtil.format(LocalDateTime.from(obj), TIMEFRAME));
    private static final JsonDeserializer<LocalDateTime> DATE_TIME_DESERIALIZER =
            (json, type, ctx) -> DateUtil.parseLocalDateTime(json.getAsJsonPrimitive().getAsString(), FORMAT);
    private static final JsonDeserializer<LocalDate> DATE_DESERIALIZER =
            (json, type, ctx) -> LocalDate.parse(json.getAsJsonPrimitive().getAsString(),
                    ofPattern(DATEFORMAT));
    private static final JsonDeserializer<LocalTime> TIME_DESERIALIZER =
            (json, type, ctx) -> LocalTime.parse(json.getAsJsonPrimitive().getAsString(),
                    ofPattern(TIMEFRAME));

    static {
        GsonBuilder builder = new GsonBuilder()
                //不转义html符号
                .disableHtmlEscaping()
                //启用复杂key
                .enableComplexMapKeySerialization()
                .setDateFormat(FORMAT)
                //序列化
                .registerTypeAdapter(LocalDateTime.class, DATE_TIME_SERIALIZER)
                .registerTypeAdapter(LocalDate.class, DATE_SERIALIZER)
                .registerTypeAdapter(LocalTime.class, TIME_SERIALIZER)
                //反序列化
                .registerTypeAdapter(LocalDateTime.class, DATE_TIME_DESERIALIZER)
                .registerTypeAdapter(LocalDate.class, DATE_DESERIALIZER)
                .registerTypeAdapter(LocalTime.class, TIME_DESERIALIZER)
                //当字段值为空或者null时，依然对字段进行转换
                .setPrettyPrinting()
                //防止特殊字符出现乱码
                .disableHtmlEscaping()
                //避免忽略null
                //.serializeNulls()
                //long转string
                .setLongSerializationPolicy(LongSerializationPolicy.STRING)
                //添加自定义适配器
                .registerTypeAdapter(TypeToken.of(Map.class).getType(), new MapTypeAdapter());

        //创建gson实例
        GSON = builder
                .setExclusionStrategies(new ExclusionStrategy() {
                    @Override
                    public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                        //包含自定义注解的直接忽略
                        return null != fieldAttributes.getAnnotation(IgnoreField.class);
                    }

                    @Override
                    public boolean shouldSkipClass(Class<?> aClass) {
                        return aClass.isAnnotationPresent(IgnoreField.class);
                    }
                })
                .create();
    }

    /**
     * 获取gson对象
     *
     * @return gson
     */
    public static Gson getGson() {
        return GSON;
    }

    /**
     * 对象转json
     *
     * @param obj obj
     * @return json
     */
    public static String toGsonString(Object obj) {
        String var2 = null;
        try {
            var2 = GSON.toJson(obj);
        } catch (Exception e) {
            log.error("对象转json失败--->", e);
        }
        return var2;
    }

    /**
     * json转对象
     *
     * @param json  json
     * @param clazz class
     * @param <T>   泛型
     * @return T
     */
    public static <T> T parseObject(String json, Class<T> clazz) {
        return GSON.fromJson(json, TypeToken.of(clazz).getType());
    }

    /**
     * json转集合
     *
     * @param json  json
     * @param clazz class
     * @param <T>   泛型
     * @return list
     */
    public static <T> List<T> parseArray(String json, Class<T> clazz) {
        log.info("需要转换为list的json数据--->{}", json);
        ParameterizedType type = new ParameterizedType(clazz);
        List<T> list = Lists.newArrayList();
        try {
            list = GSON.fromJson(json, type);
        } catch (Exception e) {
            log.error("json转list失败---->", e);
        }
        return list;
    }

    /**
     * 用于序列化集合时使用
     */
    private static class ParameterizedType implements java.lang.reflect.ParameterizedType {
        private final Class clazz;

        public ParameterizedType(Class clazz) {
            this.clazz = clazz;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return new Type[]{clazz};
        }

        @Override
        public Type getRawType() {
            return List.class;
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    }

    /**
     * 用户解决int被转换成double的问题
     */
    static class MapTypeAdapter extends TypeAdapter<Object> {
        @Override
        public void write(JsonWriter out, Object value) throws IOException {
            if (null == value) {
                out.nullValue();
                return;
            }
            TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) GSON.getAdapter(value.getClass());
            if (typeAdapter instanceof ObjectTypeAdapter) {
                out.beginObject();
                out.endObject();
                return;
            }
            typeAdapter.write(out, value);
        }

        @Override
        public Object read(JsonReader in) throws IOException {
            String point = ".";
            JsonToken token = in.peek();
            //判断字符串的实际类型
            switch (token) {
                case BEGIN_ARRAY:
                    List<Object> list = new ArrayList<>();
                    in.beginArray();
                    while (in.hasNext()) {
                        list.add(read(in));
                    }
                    in.endArray();
                    return list;

                case BEGIN_OBJECT:
                    Map<String, Object> map = new LinkedTreeMap<>();
                    in.beginObject();
                    while (in.hasNext()) {
                        map.put(in.nextName(), read(in));
                    }
                    in.endObject();
                    return map;
                case STRING:
                    return in.nextString();
                case NUMBER:
                    String s = in.nextString();
                    if (s.contains(point)) {
                        return Double.valueOf(s);
                    } else {
                        try {
                            return Integer.valueOf(s);
                        } catch (Exception e) {
                            return Long.valueOf(s);
                        }
                    }
                case BOOLEAN:
                    return in.nextBoolean();
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    throw new IllegalStateException();
            }
        }

        public MapTypeAdapter() {
            super();
        }
    }
}
